package com.ibeeking.found.file.rest.controller.v1;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ibeeking.found.common.annotation.LoginUser;
import com.ibeeking.found.common.entity.CurrentUser;
import com.ibeeking.found.common.utils.UserUtils;
import com.ibeeking.found.file.rest.config.es.FileSearch;
import com.ibeeking.found.file.rest.service.IFileService;
import com.ibeeking.found.file.rest.service.IUserFileService;
import com.ibeeking.found.file.rest.service.component.AsyncTaskComp;
import com.ibeeking.found.file.rest.service.component.FileDealComp;
import com.ibeeking.found.file.rest.service.util.QiwenFile;
import com.ibeeking.found.file.rest.service.util.QiwenFileUtil;
import com.ibeeking.found.file.rest.service.util.TreeNode;
import com.ibeeking.found.file.service.common.dos.FileDO;
import com.ibeeking.found.file.service.common.dos.UserFileDO;
import com.ibeeking.found.file.service.common.param.*;
import com.ibeeking.found.file.service.common.query.FilePageQuery;
import com.ibeeking.found.file.service.common.vo.FileDetailVO;
import com.ibeeking.found.file.service.common.vo.FileListVO;
import com.ibeeking.found.file.service.common.vo.SearchFileVO;
import com.ibeeking.nematos.constants.enums.OperateLogTypeEnum;
import com.ibeeking.nematos.exception.BusinessException;
import com.ibeeking.nematos.file.oss.factory.FileOpFactory;
import com.ibeeking.nematos.file.oss.operation.copy.Copier;
import com.ibeeking.nematos.file.oss.operation.copy.domain.CopyFile;
import com.ibeeking.nematos.log.annotation.Log;
import com.ibeeking.nematos.utils.date.DateUtil;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
/**
 * @ClassName FileController
 * @Description 该接口为文件接口，主要用来做一些文件的基本操作，如创建目录，删除，移动，复制等
 * @Author ibeeking
 * @Date 2021-06-02 18:15
 **/

@RestController
@Slf4j
@RequestMapping("/file/v1")
public class FileController {

    @Resource
    private IFileService fileServiceImpl;

    @Resource
    private IUserFileService userFileServiceImpl;

    @Resource
    private FileOpFactory fileOpFactory;

    @Resource
    private FileDealComp fileDealComp;

    @Resource
    private AsyncTaskComp asyncTaskComp;

    @Resource
    private ElasticsearchClient elasticsearchClient;

    @Value("${files.storage-type}")
    private Integer storageType;

    public static Executor executor = Executors.newFixedThreadPool(20);

    public static final String CURRENT_MODULE = "文件接口";

    @Log(logType = OperateLogTypeEnum.SAVE, describe = "创建文件")
    @PostMapping(value = "/createFile")
    public Boolean createFile(@Valid @RequestBody CreateFileParam createFileDTO) {
        try {

            Long userId = UserUtils.getUserId();
            String filePath = createFileDTO.getFilePath();
            String fileName = createFileDTO.getFileName();
            String extendName = createFileDTO.getExtendName();
            List<UserFileDO> userFiles = userFileServiceImpl.selectSameUserFile(fileName, filePath, extendName, userId);
            if (userFiles != null && !userFiles.isEmpty()) {
                throw new BusinessException("同名文件已存在");
            }
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");

            String templateFilePath = "";
            if ("docx".equals(extendName)) {
                templateFilePath = "template/Word.docx";
            } else if ("xlsx".equals(extendName)) {
                templateFilePath = "template/Excel.xlsx";
            } else if ("pptx".equals(extendName)) {
                templateFilePath = "template/PowerPoint.pptx";
            } else if ("txt".equals(extendName)) {
                templateFilePath = "template/Text.txt";
            } else if ("drawio".equals(extendName)) {
                templateFilePath = "template/Drawio.drawio";
            }
            String url2 = ClassUtils.getDefaultClassLoader().getResource("static/" + templateFilePath).getPath();
            url2 = URLDecoder.decode(url2, "UTF-8");
            FileInputStream fileInputStream = new FileInputStream(url2);
            Copier copier = fileOpFactory.getCopier();
            CopyFile copyFile = new CopyFile();
            copyFile.setExtendName(extendName);
            String fileUrl = copier.copy(fileInputStream, copyFile);

            FileDO fileBean = new FileDO();
            fileBean.setFileId(IdUtil.getSnowflakeNextId());
            fileBean.setFileSize(0L);
            fileBean.setFileUrl(fileUrl);
            fileBean.setStorageType(storageType);
            fileBean.setIdentifier(uuid);
            fileBean.setCreateTime(LocalDateTime.now());
            fileBean.setCreateBy(userId);
            fileBean.setFileStatus(1);
            boolean saveFlag = fileServiceImpl.save(fileBean);
            UserFileDO userFile = new UserFileDO();
            if (saveFlag) {
                userFile.setUserFileId(IdUtil.getSnowflakeNextId());
                userFile.setUserId(userId);
                userFile.setFileName(fileName);
                userFile.setFilePath(filePath);
                userFile.setDeleteFlag(0);
                userFile.setIsDir(0);
                userFile.setExtendName(extendName);
                userFile.setUploadTime(LocalDateTime.now());
                userFile.setFileId(fileBean.getFileId());
                userFile.setCreateTime(LocalDateTime.now());
                userFile.setCreateBy(userId);
                userFileServiceImpl.save(userFile);
            }
            return true;
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException(e.getMessage());
        }
    }

    @Log(logType = OperateLogTypeEnum.SAVE, describe = "创建文件夹")
    @PostMapping(value = "/createFold")
    public Boolean createFold(@Valid @RequestBody CreateFoldParam createFoldDto) {
        Long userId = UserUtils.getUserId();
        String filePath = createFoldDto.getFilePath();

        boolean isDirExist = fileDealComp.isDirExist(createFoldDto.getFileName(), createFoldDto.getFilePath(), userId);

        if (isDirExist) {
            throw new BusinessException("同名文件夹已存在");
        }

        UserFileDO userFile = QiwenFileUtil.getQiwenDir(userId, filePath, createFoldDto.getFileName());

        userFileServiceImpl.save(userFile);
        fileDealComp.uploadESByUserFileId(userFile.getUserFileId());
        return true;
    }

    @Log(logType = OperateLogTypeEnum.QUERY, describe = "文件搜索")
    @GetMapping(value = "/search")
    public List<SearchFileVO> searchFile(SearchFileParam searchFileDTO) {
        String userId = UserUtils.getStringUserId();
        int currentPage = (int)searchFileDTO.getCurrentPage() - 1;
        int pageCount = (int)(searchFileDTO.getPageCount() == 0 ? 10 : searchFileDTO.getPageCount());

        SearchResponse<FileSearch> search = null;
        try {
            search = elasticsearchClient.search(s -> s
                            .index("filesearch")
                            .query(_1 -> _1
                                    .bool(_2 -> _2
                                            .must(_3 -> _3
                                                    .bool(_4 -> _4
                                                            .should(_5 -> _5
                                                                    .match(_6 -> _6
                                                                            .field("fileName")
                                                                            .query(searchFileDTO.getFileName())))
                                                            .should(_5 -> _5
                                                                    .wildcard(_6 -> _6
                                                                            .field("fileName")
                                                                            .wildcard("*" + searchFileDTO.getFileName() + "*")))
                                                            .should(_5 -> _5
                                                                    .match(_6 -> _6
                                                                            .field("content")
                                                                            .query(searchFileDTO.getFileName())))
                                                            .should(_5 -> _5
                                                                    .wildcard(_6 -> _6
                                                                            .field("content")
                                                                            .wildcard("*" + searchFileDTO.getFileName() + "*")))
                                                    ))
                                            .must(_3 -> _3
                                                    .term(_4 -> _4
                                                            .field("userId")
                                                            .value(userId)))
                                    ))
                            .from(currentPage)
                            .size(pageCount)
                            .highlight(h -> h
                                    .fields("fileName", f -> f.type("plain")
                                            .preTags("<span class='keyword'>").postTags("</span>"))
                                    .encoder(HighlighterEncoder.Html))
                            ,
                    FileSearch.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        List<SearchFileVO> searchFileVOList = new ArrayList<>();
        for (Hit<FileSearch> hit : search.hits().hits()) {
            SearchFileVO searchFileVO = new SearchFileVO();
            BeanUtil.copyProperties(hit.source(), searchFileVO);
            searchFileVO.setHighLight(hit.highlight());
            searchFileVOList.add(searchFileVO);
            asyncTaskComp.checkESUserFileId(searchFileVO.getUserFileId());
        }
        return searchFileVOList;
    }


    @Log(logType = OperateLogTypeEnum.MODIFY, describe = "文件重命名")
    @PostMapping(value = "/renamefile")
    public Boolean renameFile(@RequestBody RenameFileParam renameFileDto) {
        Long userId = UserUtils.getUserId();
        UserFileDO userFile = userFileServiceImpl.getById(renameFileDto.getUserFileId());

        List<UserFileDO> userFiles = userFileServiceImpl.selectUserFileByNameAndPath(renameFileDto.getFileName(), userFile.getFilePath(), userId);
        if (userFiles != null && !userFiles.isEmpty()) {
            throw new BusinessException("同名文件已存在");
        }

        LambdaUpdateWrapper<UserFileDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.set(UserFileDO::getFileName, renameFileDto.getFileName())
                .set(UserFileDO::getUploadTime, DateUtil.getCurrentTime())
                .eq(UserFileDO::getUserFileId, renameFileDto.getUserFileId());
        userFileServiceImpl.update(lambdaUpdateWrapper);
        if (1 == userFile.getIsDir()) {
            List<UserFileDO> list = userFileServiceImpl.selectUserFileByLikeRightFilePath(new QiwenFile(userFile.getFilePath(), userFile.getFileName(), true).getPath(), userId);

            for (UserFileDO newUserFile : list) {
                String escapedPattern = Pattern.quote(new QiwenFile(userFile.getFilePath(), userFile.getFileName(), userFile.getIsDir() == 1).getPath());
                newUserFile.setFilePath(newUserFile.getFilePath().replaceFirst(escapedPattern,
                        new QiwenFile(userFile.getFilePath(), renameFileDto.getFileName(), userFile.getIsDir() == 1).getPath()));
                userFileServiceImpl.updateById(newUserFile);
            }
        }
        fileDealComp.uploadESByUserFileId(renameFileDto.getUserFileId());
        return true;
    }

    @Log(logType = OperateLogTypeEnum.QUERY, describe = "获取文件列表")
    @PostMapping(value = "/getfilelist")
    public Page<FileListVO> getFileList(@LoginUser CurrentUser loginUer, @RequestBody FilePageQuery pageQuery){
        if ("0".equals(pageQuery.getFileType())) {
            Page<FileListVO> fileList = userFileServiceImpl.userFileList(loginUer.getId(), pageQuery.getFilePath(), pageQuery.getPageNum(), pageQuery.getPageSize());
            return fileList;
        } else {
            Page<FileListVO> fileList = userFileServiceImpl.getFileByFileType(Integer.valueOf(pageQuery.getFileType()), pageQuery.getPageNum(), pageQuery.getPageSize(), loginUer.getId());
            return fileList;
        }
    }


    @Log(logType = OperateLogTypeEnum.DEL, describe = "批量删除文件")
    @PostMapping(value = "/batchdeletefile")
    public Boolean deleteImageByIds(@RequestBody BatchDeleteFileParam batchDeleteFileDto) {
        Long userId = UserUtils.getUserId();
        String userFileIds = batchDeleteFileDto.getUserFileIds();
        String[] userFileIdList = userFileIds.split(",");
        userFileServiceImpl.update(new UpdateWrapper<UserFileDO>().lambda().set(UserFileDO::getDeleteFlag, 1).in(UserFileDO::getUserFileId, Arrays.asList(userFileIdList)));
        for (String userFileId : userFileIdList) {
            executor.execute(()->{
                userFileServiceImpl.deleteUserFile(Long.valueOf(userFileId), userId);
            });

            fileDealComp.deleteESByUserFileId(Long.valueOf(userFileId));
        }

        return true;
    }

    @Log(logType = OperateLogTypeEnum.DEL, describe = "删除文件")
    @PostMapping(value = "/deletefile")
    public Boolean deleteFile(@RequestBody DeleteFileParam deleteFileDto) {
        Long userId = UserUtils.getUserId();
        userFileServiceImpl.deleteUserFile(deleteFileDto.getUserFileId(), userId);
        fileDealComp.deleteESByUserFileId(deleteFileDto.getUserFileId());

        return true;

    }


    @Log(logType = OperateLogTypeEnum.UNKNOWN, describe = "解压文件")
    @PostMapping(value = "/unzipfile")
    public Boolean unzipFile(@RequestBody UnzipFileParam unzipFileDto) {

        try {
            fileServiceImpl.unzipFile(unzipFileDto.getUserFileId(), unzipFileDto.getUnzipMode(), unzipFileDto.getFilePath());
        } catch (BusinessException e) {
            throw new BusinessException(e.getMessage());
        }

        return true;

    }

    @Log(logType = OperateLogTypeEnum.MODIFY, describe = "文件复制")
    @PostMapping(value = "/copyfile")
    public Boolean copyFile(@RequestBody CopyFileParam copyFileDTO) {
        Long userId = UserUtils.getUserId();
        String filePath = copyFileDTO.getFilePath();
        String userFileIds = copyFileDTO.getUserFileIds();
        String[] userFileIdArr = userFileIds.split(",");
        for (String userFileId : userFileIdArr) {
            UserFileDO userFile = userFileServiceImpl.getById(Long.valueOf(userFileId));
            String oldfilePath = userFile.getFilePath();
            String fileName = userFile.getFileName();
            if (userFile.isDirectory()) {
                QiwenFile qiwenFile = new QiwenFile(oldfilePath, fileName, true);
                if (filePath.startsWith(qiwenFile.getPath() + QiwenFile.separator) || filePath.equals(qiwenFile.getPath())) {
                    throw new BusinessException("原路径与目标路径冲突，不能复制");
                }
            }
            userFileServiceImpl.userFileCopy(userId, Long.valueOf(userFileId), filePath);
            fileDealComp.deleteRepeatSubDirFile(filePath, userId);
        }

        return true;

    }

    @Log(logType = OperateLogTypeEnum.MODIFY, describe = "文件移动")
    @PostMapping(value = "/movefile")
    public Boolean moveFile(@RequestBody MoveFileParam moveFileDto) {
        Long userId = UserUtils.getUserId();
        UserFileDO userFile = userFileServiceImpl.getById(moveFileDto.getUserFileId());
        String oldfilePath = userFile.getFilePath();
        String newfilePath = moveFileDto.getFilePath();
        String fileName = userFile.getFileName();
        String extendName = userFile.getExtendName();
        if (StringUtil.isEmpty(extendName)) {
            QiwenFile qiwenFile = new QiwenFile(oldfilePath, fileName, true);
            if (newfilePath.startsWith(qiwenFile.getPath() + QiwenFile.separator) || newfilePath.equals(qiwenFile.getPath())) {
                throw new BusinessException("原路径与目标路径冲突，不能移动");
            }
        }

        userFileServiceImpl.updateFilepathByUserFileId(moveFileDto.getUserFileId(), newfilePath, userId);

        fileDealComp.deleteRepeatSubDirFile(newfilePath, userId);
        return true;

    }

    @Log(logType = OperateLogTypeEnum.MODIFY, describe = "批量移动文件")
    @PostMapping(value = "/batchmovefile")
    public Boolean batchMoveFile(@RequestBody BatchMoveFileParam batchMoveFileDto) {
        Long userId = UserUtils.getUserId();
        String newfilePath = batchMoveFileDto.getFilePath();

        String userFileIds = batchMoveFileDto.getUserFileIds();
        String[] userFileIdArr = userFileIds.split(",");

        for (String userFileId : userFileIdArr) {
            UserFileDO userFile = userFileServiceImpl.getById(userFileId);
            if (StringUtil.isEmpty(userFile.getExtendName())) {
                QiwenFile qiwenFile = new QiwenFile(userFile.getFilePath(), userFile.getFileName(), true);
                if (newfilePath.startsWith(qiwenFile.getPath() + QiwenFile.separator) || newfilePath.equals(qiwenFile.getPath())) {
                    throw new BusinessException("原路径与目标路径冲突，不能移动");
                }
            }
            userFileServiceImpl.updateFilepathByUserFileId(userFile.getUserFileId(), newfilePath, userId);
        }

        return true;

    }

    @Log(logType = OperateLogTypeEnum.QUERY, describe = "获取文件树")
    @GetMapping(value = "/getfiletree")
    public TreeNode getFileTree() {
        Long userId = UserUtils.getUserId();

        List<UserFileDO> userFileList = userFileServiceImpl.selectFilePathTreeByUserId(userId);
        TreeNode resultTreeNode = new TreeNode();
        resultTreeNode.setLabel(QiwenFile.separator);
        resultTreeNode.setId(0L);
        long id = 1;
        for (int i = 0; i < userFileList.size(); i++){
            UserFileDO userFile = userFileList.get(i);
            QiwenFile qiwenFile = new QiwenFile(userFile.getFilePath(), userFile.getFileName(), false);
            String filePath = qiwenFile.getPath();

            Queue<String> queue = new LinkedList<>();

            String[] strArr = filePath.split(QiwenFile.separator);
            for (int j = 0; j < strArr.length; j++){
                if (!"".equals(strArr[j]) && strArr[j] != null){
                    queue.add(strArr[j]);
                }

            }
            if (queue.size() == 0){
                continue;
            }

            resultTreeNode = fileDealComp.insertTreeNode(resultTreeNode, id++, QiwenFile.separator, queue);


        }
        List<TreeNode> treeNodeList = resultTreeNode.getChildren();
        Collections.sort(treeNodeList, (o1, o2) -> {
            long i = o1.getId() - o2.getId();
            return (int) i;
        });

        return resultTreeNode;

    }

    @Log(logType = OperateLogTypeEnum.MODIFY, describe = "修改文件")
    @PostMapping(value = "/update")
    public Boolean updateFile(@RequestBody UpdateFileParam updateFileDTO) {
        Long userId = UserUtils.getUserId();
        UserFileDO userFile = userFileServiceImpl.getById(updateFileDTO.getUserFileId());
        FileDO fileBean = fileServiceImpl.getById(userFile.getFileId());
        Long pointCount = fileServiceImpl.getFilePointCount(userFile.getFileId());
        String fileUrl = fileBean.getFileUrl();
        if (pointCount > 1) {
            fileUrl = fileDealComp.copyFile(fileBean, userFile);
        }
        String content = updateFileDTO.getFileContent();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content.getBytes());
        try {
            int fileSize = byteArrayInputStream.available();
            fileDealComp.saveFileInputStream(fileBean.getStorageType(), fileUrl, byteArrayInputStream);

            String md5Str = fileDealComp.getIdentifierByFile(fileUrl, fileBean.getStorageType());

            fileServiceImpl.updateFileDetail(userFile.getUserFileId(), md5Str, fileSize);


        } catch (Exception e) {
            throw new BusinessException("修改文件异常");
        } finally {
            try {
                byteArrayInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    @Log(logType = OperateLogTypeEnum.QUERY, describe = "查询文件详情")
    @GetMapping(value = "/detail/{fileId}")
    public FileDetailVO queryFileDetail(@PathVariable("fileId")  String userFileId){
        FileDetailVO vo = fileServiceImpl.getFileDetail(Long.valueOf(userFileId));
        return vo;
    }




}
